edge-functions.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { useQueryClient } from '@tanstack/react-query'
  2. import { useParams } from 'common'
  3. import dayjs from 'dayjs'
  4. import { ArrowRight, RefreshCw } from 'lucide-react'
  5. import { parseAsJson, useQueryState } from 'nuqs'
  6. import { useMemo, useState } from 'react'
  7. import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
  8. import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
  9. import {
  10. EDGE_FUNCTION_REGIONS,
  11. REPORT_DATERANGE_HELPER_LABELS,
  12. } from '@/components/interfaces/Reports/Reports.constants'
  13. import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
  14. import { ReportChartV2 } from '@/components/interfaces/Reports/v2/ReportChartV2'
  15. import {
  16. numericFilterSchema,
  17. ReportsNumericFilter,
  18. } from '@/components/interfaces/Reports/v2/ReportsNumericFilter'
  19. import {
  20. ReportsSelectFilter,
  21. selectFilterSchema,
  22. } from '@/components/interfaces/Reports/v2/ReportsSelectFilter'
  23. import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
  24. import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
  25. import DefaultLayout from '@/components/layouts/DefaultLayout'
  26. import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
  27. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  28. import { ReportSettings } from '@/components/ui/Charts/ReportSettings'
  29. import { useChartHoverState } from '@/components/ui/Charts/useChartHoverState'
  30. import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
  31. import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
  32. import { edgeFunctionReports } from '@/data/reports/v2/edge-functions.config'
  33. import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
  34. import { BASE_PATH } from '@/lib/constants'
  35. import type { NextPageWithLayout } from '@/types'
  36. const EdgeFunctionsReportV2: NextPageWithLayout = () => {
  37. return (
  38. <ReportPadding>
  39. <EdgeFunctionsUsage />
  40. </ReportPadding>
  41. )
  42. }
  43. EdgeFunctionsReportV2.getLayout = (page) => (
  44. <DefaultLayout>
  45. <ObservabilityLayout title="Edge Functions">{page}</ObservabilityLayout>
  46. </DefaultLayout>
  47. )
  48. export default EdgeFunctionsReportV2
  49. const EdgeFunctionsUsage = () => {
  50. const { ref } = useParams()
  51. const { data: functions } = useEdgeFunctionsQuery({
  52. projectRef: ref,
  53. })
  54. const chartSyncId = `edge-functions-${ref}`
  55. useChartHoverState(chartSyncId)
  56. // Filters
  57. const [statusCodeFilter, setStatusCodeFilter] = useQueryState(
  58. 'status_code',
  59. parseAsJson(numericFilterSchema.parse)
  60. )
  61. const [regionFilter, setRegionFilter] = useQueryState(
  62. 'region',
  63. parseAsJson(selectFilterSchema.parse)
  64. )
  65. const [executionTimeFilter, setExecutionTimeFilter] = useQueryState(
  66. 'execution_time',
  67. parseAsJson(numericFilterSchema.parse)
  68. )
  69. const [functionFilter, setFunctionFilter] = useQueryState(
  70. 'functions',
  71. parseAsJson(selectFilterSchema.parse)
  72. )
  73. const {
  74. selectedDateRange,
  75. updateDateRange,
  76. datePickerValue,
  77. datePickerHelpers,
  78. showUpgradePrompt,
  79. setShowUpgradePrompt,
  80. handleDatePickerChange,
  81. } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
  82. const queryClient = useQueryClient()
  83. const [isRefreshing, setIsRefreshing] = useState(false)
  84. const reportConfig = useMemo(() => {
  85. return edgeFunctionReports({
  86. projectRef: ref!,
  87. functions: functions ?? [],
  88. startDate: selectedDateRange?.period_start?.date ?? '',
  89. endDate: selectedDateRange?.period_end?.date ?? '',
  90. interval: selectedDateRange?.interval ?? 'minute',
  91. filters: {
  92. functions: functionFilter ?? [],
  93. status_code: statusCodeFilter,
  94. region: regionFilter ?? [],
  95. execution_time: executionTimeFilter,
  96. },
  97. })
  98. }, [
  99. ref,
  100. functions,
  101. selectedDateRange,
  102. functionFilter,
  103. statusCodeFilter,
  104. regionFilter,
  105. executionTimeFilter,
  106. ])
  107. const onRefreshReport = useRefreshHandler(
  108. datePickerValue,
  109. datePickerHelpers,
  110. handleDatePickerChange,
  111. async () => {
  112. if (!selectedDateRange) return
  113. setIsRefreshing(true)
  114. queryClient.invalidateQueries({ queryKey: ['projects', ref, 'report-v2'] })
  115. setTimeout(() => setIsRefreshing(false), 1000)
  116. }
  117. )
  118. return (
  119. <>
  120. <ReportHeader title="Edge Functions" showDatabaseSelector={false} />
  121. <ReportStickyNav
  122. content={
  123. <div className="flex flex-col gap-2">
  124. <div className="flex items-center gap-2">
  125. <ButtonTooltip
  126. type="default"
  127. disabled={isRefreshing}
  128. icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
  129. className="w-7"
  130. tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
  131. onClick={onRefreshReport}
  132. />
  133. <ReportSettings chartId="edge-functions-charts" />
  134. <LogsDatePicker
  135. align="start"
  136. value={datePickerValue}
  137. helpers={datePickerHelpers}
  138. onSubmit={handleDatePickerChange}
  139. />
  140. <UpgradePrompt
  141. show={showUpgradePrompt}
  142. setShowUpgradePrompt={setShowUpgradePrompt}
  143. title="Report date range"
  144. description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
  145. source="edgeFunctionsReportDateRange"
  146. />
  147. {selectedDateRange && (
  148. <div className="flex items-center gap-x-2 text-xs">
  149. <p className="text-foreground-light">
  150. {dayjs(selectedDateRange.period_start.date).format('MMM D, h:mma')}
  151. </p>
  152. <p className="text-foreground-light">
  153. <ArrowRight size={12} />
  154. </p>
  155. <p className="text-foreground-light">
  156. {dayjs(selectedDateRange.period_end.date).format('MMM D, h:mma')}
  157. </p>
  158. </div>
  159. )}
  160. </div>
  161. <div className="w-full flex items-center gap-2 flex-wrap">
  162. <ReportsSelectFilter
  163. label="Function"
  164. options={
  165. functions?.map((fn: { name: string; id: string }) => ({
  166. label: fn.name,
  167. value: fn.id,
  168. })) ?? []
  169. }
  170. value={functionFilter ?? []}
  171. onChange={setFunctionFilter}
  172. isLoading={isRefreshing}
  173. showSearch
  174. />
  175. <ReportsNumericFilter
  176. label="Status Code"
  177. value={statusCodeFilter}
  178. onChange={setStatusCodeFilter}
  179. defaultOperator="="
  180. isLoading={isRefreshing}
  181. />
  182. <ReportsNumericFilter
  183. label="Execution Time"
  184. value={executionTimeFilter}
  185. onChange={setExecutionTimeFilter}
  186. placeholder="Enter time in ms"
  187. min={0}
  188. max={99999}
  189. defaultOperator=">="
  190. isLoading={isRefreshing}
  191. />
  192. <ReportsSelectFilter
  193. label="Region"
  194. options={EDGE_FUNCTION_REGIONS.map((region) => ({
  195. value: region.key,
  196. label: (
  197. <div className="flex items-center gap-x-2">
  198. <img
  199. src={`${BASE_PATH}/img/regions/${region.key}.svg`}
  200. alt={region.key}
  201. className="w-4 h-4"
  202. />
  203. <div className="flex flex-wrap gap-x-2 items-center">
  204. <span className="text-foreground text-xs">{region.label}</span>
  205. <span className="text-foreground-lighter text-xs">{region.key}</span>
  206. </div>
  207. </div>
  208. ),
  209. }))}
  210. value={regionFilter ?? []}
  211. onChange={setRegionFilter}
  212. showSearch
  213. />
  214. </div>
  215. </div>
  216. }
  217. >
  218. <div className="mt-8 flex flex-col gap-4 pb-8">
  219. {selectedDateRange &&
  220. reportConfig
  221. .filter((report) => !report.hide)
  222. .map((report) => (
  223. <ReportChartV2
  224. key={`${report.id}`}
  225. report={report}
  226. projectRef={ref!}
  227. interval={selectedDateRange.interval}
  228. startDate={selectedDateRange?.period_start?.date}
  229. endDate={selectedDateRange?.period_end?.date}
  230. updateDateRange={updateDateRange}
  231. syncId={chartSyncId}
  232. filters={{
  233. functions: functionFilter,
  234. status_code: statusCodeFilter,
  235. region: regionFilter,
  236. execution_time: executionTimeFilter,
  237. }}
  238. />
  239. ))}
  240. </div>
  241. </ReportStickyNav>
  242. <div className="pb-8">
  243. <ObservabilityLink />
  244. </div>
  245. </>
  246. )
  247. }